EDA do dataset da Olist de e-commerce no
Brasil
Este conjunto de dados é uma representação de informações detalhadas
sobre 100 mil pedidos feitos na Olist Store durante o período de 2016 a
2018, abrangendo diversas plataformas de venda no Brasil. Os recursos
presentes neste conjunto de dados nos permitem explorar os pedidos sob
diversas perspectivas, desde o status do pedido, preço, pagamento e
desempenho de frete, até a localização do cliente, atributos dos
produtos e, por fim, avaliações escritas pelos clientes. É importante
ressaltar que esses dados comerciais são reais, embora tenham sido
anonimizados.
Esse conjunto de dados foi gentilmente fornecido pela Olist, a maior
loja de departamentos em marketplaces brasileiros. A Olist conecta
pequenos negócios de todo o Brasil a canais de venda de forma
simplificada, com um único contrato. Esses comerciantes têm a capacidade
de vender seus produtos através da Olist Store e enviá-los diretamente
aos clientes utilizando os parceiros logísticos da Olist.
No sistema da Olist cada pedido é designado a um
customerid único. Isso significa que cada consumidor
terá diferentes ids para diferentes pedidos. O
propósito de ter um customerunique_id único por pessoa
(como se fosse um CPF) na base é permitir identificar consumidores que
fizeram recompras na loja. Caso contrário, você encontraria que cada
ordem sempre tivesse diferentes consumidores associados.
customers <- read_csv('data/olist_customers_dataset.csv')
orders <- read_csv('data/olist_orders_dataset.csv')
reviews <- read_csv('data/olist_order_reviews_dataset.csv')
payments <- read_csv('data/olist_order_payments_dataset.csv')
products <- read_csv('data/olist_products_dataset.csv')
sellers <- read_csv('data/olist_sellers_dataset.csv')
categories <- read_csv('data/product_category_name_translation.csv')
geolocation <- read_csv('data/olist_geolocation_dataset.csv')
order_items <- read_csv('data/olist_order_items_dataset.csv')
# Juntando os dados em uma base única com exceção dos dados de geolocalização
complete_data <- customers %>%
left_join(orders) %>%
left_join(order_items) %>% # Uma ordem pode ter muito itens e por isso a base expande aqui
left_join(reviews) %>%
left_join(payments) %>%
left_join(products) %>%
left_join(sellers) %>%
left_join(categories)
Primeiras observações nos dados
skim(complete_data)
── Data Summary ────────────────────────
Values
Name complete_data
Number of rows 119143
Number of columns 40
_______________________
Column type frequency:
character 18
numeric 14
POSIXct 8
________________________
Group variables None
A análise por item expande a base, mas a maioria dos pedidos (87.6%)
consiste em apenas 1 item, como já era de se esperar. Para simplificar,
vamos limitar nossa análise somente a pedidos com um item.
order_items %>%
count(order_item_id) %>% # Número de itens do mesmo pedido
mutate(prop = round(n / sum(n) * 100, 2))
complete_data <- complete_data %>%
filter(order_item_id == 1)
Transformando os dados
Nesta etapa, após a análise inicial da estrutura dos dados, criamos
variáveis que serão úteis na análise e reduzimos o escopo da análise
somente para entregas concluídas (order_status == ‘delivered’).
Variável de review alto: “1” se score do review for 5, e “0” caso
contrário.
Variável de diferença no prazo de entrega: diferença entre o
prazo de entrega estipulado para o cliente e a data de entrega
propriamente dita.
Variável de proporção do frete: relaciona valores de Frete e
Preço.
Variável de frete gratuito: binária
analysis <- complete_data %>%
mutate(high_review = ifelse(review_score == 5, 'Max. Score', 'Less than 5'),
high_review_binary = ifelse(review_score == 5, 1, 0),
order_delivered_customer_date = as.Date(order_delivered_customer_date),
order_estimated_delivery_date = as.Date(order_estimated_delivery_date),
delivery_delay = as.numeric(order_delivered_customer_date - order_estimated_delivery_date),
late = ifelse(delivery_delay > 0, 1, 0),
freight_prop = freight_value / price,
free_freight = ifelse(freight_value == 0, 1, 0)) %>%
select(customer_state,
order_status,
price,
freight_value,
payment_type,
payment_value,
product_category_name,
product_photos_qty,
review_score,
high_review,
high_review_binary,
delivery_delay,
late,
order_estimated_delivery_date,
order_delivered_customer_date,
review_comment_title,
review_comment_message,
freight_prop,
free_freight,
customer_city,
seller_city) %>%
filter(order_status == 'delivered')
Também precisamos entender a distribuição dos dados faltantes no
dataset, por isso usaremos o pacote vis_dat. Como nossa base de dados é
muito grande, vamos pegar uma parte menor (30% do total) e tentar
entender quais colunas possuem muitos dados faltando.
Naturalmente, as colunas com maior quantidade de dados faltantes são
as que possuem preenchimento opcional: título e mensagem do comentário
de revisão do produto.
set.seed(2101)
vis_dat(analysis %>% sample_frac(0.3))

map_df(analysis, ~sum(is.na(.))) %>%
gather() %>%
arrange(desc(value))
Outro fato que precisamos corrigir antes de qualquer análise é a
existência de valores nulos na coluna “late”, que é uma coluna binária
indicando a presença ou ausência de um atraso na entrega. Isso ocorre
porque alguns pedidos não possuem data de entrega. Por isso iremos
remover essas ocorrências.
analysis <- analysis %>%
drop_na(late)
Por último, vamos checar as cidades com mais pedidos entregues.
analysis %>%
count(customer_city) %>%
mutate(prop = round(n / sum(n) * 100, 2)) %>%
arrange(desc(n))
Exploração e visualização de variáveis
Avaliação do produto (1 a 5)
analysis %>%
count(review_score) %>%
ggplot(aes(x = review_score, y = n)) +
geom_bar(stat ='identity') +
geom_text(aes(label = n), vjust = -0.5, size = 3)

Nota máxima: sim ou não
analysis %>%
count(high_review) %>%
ggplot(aes(x = high_review, y = n)) +
geom_bar(stat ='identity') +
geom_text(aes(label = n), vjust = -0.5, size = 3)

Pedidos por UF
analysis %>%
count(customer_state) %>%
ggplot(aes(x = customer_state, y = n)) +
geom_bar(stat ='identity') +
geom_text(aes(label = n), vjust = -0.5, size = 3)

Pedidos por categoria do produto
analysis %>%
count(product_category_name, sort = T) %>%
mutate(prop = round(n / sum(n) * 100, 2))
Análise de preços
summary(analysis$price)
Min. 1st Qu. Median Mean 3rd Qu. Max.
0.85 41.00 79.00 125.17 139.00 6735.00
p1 <- analysis %>%
ggplot(aes(y = price)) +
geom_boxplot() +
ggtitle('Boxplot')
p2 <- analysis %>%
ggplot(aes(x = price)) +
geom_density() +
ggtitle('Densidade')
grid.arrange(p1, p2, nrow = 1)

Essas análises nos informam que a variável Preço é extremamente
assimétrica, inclusive com outliers com preços acima de R$ 6000. O mais
comum aqui, caso seja desejável incluí-la na modelagem, seria
transformar essa variável e estabilizá-la. Por isso, a partir de agora
usaremos o logaritmo da variável Preço.
Lembrando que o Boxplot pode ser problemático por não refletir a
distribuição dos dados. Veremos adiante um exemplo melhor, com Violin
Plots.
p1log <- analysis %>%
ggplot(aes(y = log(price))) +
geom_boxplot() +
ggtitle('Boxplot')
p2log <- analysis %>%
ggplot(aes(x = log(price))) +
geom_density() +
ggtitle('Densidade')
grid.arrange(p1log, p2log, nrow = 1)

Análise de frete
summary(analysis$freight_value)
Min. 1st Qu. Median Mean 3rd Qu. Max.
0.00 13.32 16.39 20.20 21.23 409.68
75% dos produtos tiveram um frete inferior a 21 reais, mas é possível
ver que um pedido em específico teve um frete superior a 400 reais!
analysis %>%
ggplot(aes(x = log(freight_value))) +
geom_density() +
ggtitle('Variável Frete com Transformação Logarítmica')

A variável de Frete parece ser mais problemática, pois tem valores 0
e alguns saltos. A melhor maneira de identificar rapidamente onde estão
esses pontos de corte é através de interatividade gráfica!
(analysis %>%
filter(freight_value > 0) %>% # Tira os fretes gratuitos
ggplot(aes(x = freight_value)) +
geom_density()) %>% ggplotly()
O ponto no entorno do 10 parece um ponto de atenção pela subida
íngreme após ele. Temos muitos valores de frete repetidos. Talvez seria
interessante transformar essa variável em categórica.
Além disso, o frete deve ter relação com a distância entre cliente e
vendedor, e uma análise futura poderia levar isso em consideração
fazendo algum tipo de manipulação de localização, mas talvez os dados
não permitam fazer essa análise de maneira fácil.
Análise de frete sobre preço
summary(analysis$freight_prop)
Min. 1st Qu. Median Mean 3rd Qu. Max.
0.0000 0.1320 0.2249 0.3110 0.3836 21.4471
Aqui mais uma vez vemos uma proporção absurda de frete sobre preço.
Em um pedido específico, o frete correspondeu a 21.4% do valor total da
compra.
analysis %>%
ggplot(aes(x = log(freight_prop))) +
geom_density()

O logaritmo dessa variável, quando o frete é zero, resulta em
-Infinito. Isso causaria problemas na hora de criar os modelos, mas
podemos resolver facilmente: basta acrescentar um valor irrisório ao
frete, de modo que a divisão não resulte em zero.
Análise de frete x preço
analysis %>%
ggplot(aes(x = log(price),
y = log(freight_value))) +
geom_point() +
geom_smooth()

Fretes gratuitos
analysis %>%
count(free_freight, sort = T) %>%
mutate(prop = n / sum(n))
Os pedidos com frete gratuito equivalem a menos de 0,4% do total. É
uma porção quase irrelevante dos dados, então podemos considerar remover
esses dados para facilitar a modelagem futura.
Análise de atrasos nas entregas
summary(as.numeric(analysis$delivery_delay))
Min. 1st Qu. Median Mean 3rd Qu. Max.
-147.0 -17.0 -12.0 -11.9 -7.0 188.0
analysis %>%
count(late) %>%
mutate(prop = n / sum(n))
Podemos perceber que 75% dos pedidos chegaram com no mínimo 7 dias de
antecedência, mas existem também pedidos com atrasos absurdos, chegando
a 6 meses. Também descobrimos que menos de 7% dos pedidos analisados
foram entregues com atraso.
analysis %>%
ggplot(aes(x = delivery_delay)) +
geom_density()

Aqui podemos ver uma leve inclinação a partir do 0, indicando os
pedidos atrasados. O inclínio é bem pequeno e já sabemos que houveram
relativamente poucos pedidos atrasados.
Quantidade de fotos do produto
Muitos produtos em marketplaces costumam contar com fotos
ilustrativas do produto, que ajudam a guiar o cliente na hora da
compra.
analysis %>%
count(product_photos_qty) %>%
mutate(prop = n / sum(n))
Múltiplas densidades Preço vs. Categoria
Vamos analisar a densidade do preço por categoria. Novamente usamos
fct_lump para agrupar as categorias fora do top 7 em
frequência e criamos um gráfico com uma curva de densidade para o preço
vs. cada categoria. Como as curvas se sobrepõem, a visualização é
dificultada.
analysis %>%
mutate(cat_product_category_name = fct_lump(product_category_name, n = 7)) %>%
ggplot(aes(x = log(price), fill = cat_product_category_name)) +
geom_density(alpha = 0.35)

Um modo melhor de visualizar seria colocar a base de cada curva em um
nível diferente. O gráfico acima nos mostrava a existência de NAs na
coluna, então vamos também remover.
analysis %>%
filter(!is.na(product_category_name)) %>%
mutate(cat_product_category_name = fct_lump(product_category_name, n = 7),
log_price = log(price)) %>%
ggplot(aes(x = log_price,
y = cat_product_category_name,
fill = stat(x))) +
geom_density_ridges_gradient(scale = 3, rel_min_height = 0.001) +
scale_fill_viridis_c(name = "Log do Preço")

Outro modo de analisar os valores por categoria seria criando um
boxplot para cada, sabendo da limitação natural do boxplot em não
refletir bem a distribuição/frequência dos dados.
analysis %>%
filter(!is.na(product_category_name)) %>%
mutate(cat_product_category_name = fct_lump(product_category_name, n = 7),
log_price = log(price)) %>%
ggplot(aes(fill = cat_product_category_name,
y = log_price,
x = cat_product_category_name)) +
geom_boxplot() +
theme(legend.title = element_blank(),
axis.text.x = element_text(angle = 45))

Com algumas mudanças no código anterior, podemos transformar os box
plots em violin plots, que mostram com clareza a distribuição dos dados.
A limitação natural do violin plot no ggplot em R é a ausência das
linhas percentis, então podemos adicionar um box plot dentro de cada
violin plot e obter o “melhor dos dois mundos”
analysis %>%
filter(!is.na(product_category_name)) %>%
mutate(cat_product_category_name = fct_lump(product_category_name, n = 7),
log_price = log(price)) %>%
ggplot(aes(fill = cat_product_category_name,
y = log_price,
x = cat_product_category_name)) +
geom_violin(trim = FALSE) + # trim = FALSE shows the full distribution
geom_boxplot(width = 0.2, outlier.shape = NA, position = position_dodge(width = 0.75)) +
theme(legend.title = element_blank(),
axis.text.x = element_text(angle = 45))

Análises bivariadas
Estado vs. Avaliação do cliente
analysis %>%
filter(!is.na(high_review_binary)) %>%
group_by(customer_state) %>%
summarize(
n = n(),
max_reviews = sum(high_review_binary),
max_reviews_prop = mean(high_review_binary)) %>%
arrange(desc(max_reviews_prop))
Mesmo São Paulo sendo de maneira disparada o estado com mais pedidos,
também é o estado mais satisfeito: 61.7% dos pedidos foram avaliados com
nota máxima. Vamos agora testar uma visualização interativa com Plotly
que nos permitirá ver exatamente o número de pedidos por estado e a
“taxa de satisfação” de cada.
Aqui vemos que o estado mais insatisfeito seria o Maranhão, onde
apenas 48.3% dos 739 pedidos foram avaliados com a nota máxima. De
qualquer maneira, não é possível traçar um paralelo entre estado e
probabilidade de avaliação máxima.
analysis %>%
plota_tx_interesse(var_x = 'customer_state',
var_y = 'high_review',
flag_interesse = 'Max. Score')
Vamos tentar mudar a escala da visualização para obter maior
clareza.
analysis %>%
plota_tx_interesse(var_x = 'customer_state',
var_y = 'high_review',
flag_interesse = 'Max. Score',
ylim = NA)
Categoria do produto vs. Avaliação do cliente
analysis %>%
plota_tx_interesse(var_x = 'product_category_name',
var_y = 'high_review',
flag_interesse = 'Max. Score',
ylim = NA)
Temos muitas classes diferentes e isso acaba complicando muito a
visualização dos dados. Por isso vamos pegar as 10 mais presentes e
agrupar o resto em uma 11ª variável, chamada de “Other”. Isso é possível
através da função fct_lump a seguir
analysis %>%
mutate(product_category_name_cat = fct_lump(product_category_name, 10)) %>%
plota_tx_interesse(var_x = 'product_category_name_cat',
var_y = 'high_review',
flag_interesse = 'Max. Score',
ylim = NA)
Agora com o gráfico muito mais legível, podemos perceber rapidamente
que as 3 categorias com maior taxa de satisfação do cliente (avaliação 5
estrelas) são:
Brinquedos (62.8%)
Beleza e saúde (62.1%)
Esporte e lazer (61.4%)
Preço vs. Avaliação do cliente
analysis %>%
group_by(high_review) %>%
summarize(n = n(),
mean = mean(price, na.rm = T),
sd = sd(price, na.rm = T),
min = min(price, na.rm = T),
max = max(price, na.rm = T))
Frete vs. Avaliação do cliente
analysis %>%
group_by(high_review) %>%
summarize(n = n(),
mean = mean(freight_value, na.rm = T),
sd = sd(freight_value, na.rm = T),
min = min(freight_value, na.rm = T),
max = max(freight_value, na.rm = T))
Frete sobre preço vs. Avaliação do cliente
analysis %>%
group_by(high_review) %>%
summarize(n = n(),
mean = mean(freight_prop, na.rm = T),
sd = sd(freight_prop, na.rm = T),
min = min(freight_prop, na.rm = T),
max = max(freight_prop, na.rm = T))
Preço vs. Avaliação do cliente
analysis %>%
mutate(log_price = log(price)) %>%
ggstatsplot::ggbetweenstats(
x = high_review,
y = log_price,
title = "Log do Preço vs. Avaliação do Cliente")

Atraso na entrega vs. Avaliação do cliente
Aqui podemos perceber uma clara diferença, sendo essa uma variável
que claramente impacta na Avaliação Máxima do cliente. A curva vermelha
após o 0 indica que uma grande parte dos pedidos atrasados não recebe
nota máxima.
analysis %>%
filter(!is.na(high_review)) %>%
ggplot(aes(x = delivery_delay, fill = as.factor(high_review))) +
geom_density(alpha = 0.5)

Com poucas linhas de código, podemos perceber que
83,6% dos pedidos atrasados não foram avaliados com
nota máxima
analysis %>%
filter(!is.na(high_review)) %>%
filter(delivery_delay > 0) %>%
count(high_review) %>%
mutate(prob = n / sum(n) * 100)
Variável binária de atraso vs. Avaliação do cliente
analysis %>%
mutate(late = ifelse(late == 1, 'Yes', 'No')) %>%
plota_tx_interesse(var_x = 'late',
var_y = 'high_review',
flag_interesse = 'Max. Score',
ylim = NA)
Quantidade de fotos vs. Avaliação do cliente
Antes de fazer essa análise, substituímos NA por 0 fotos. De qualquer
maneira, não é possível notar qualquer relação.
analysis %>%
replace_na(list(product_photos_qty = 0)) %>%
plota_tx_interesse(var_x = 'product_photos_qty',
var_y = 'high_review',
flag_interesse = 'Max. Score')
Análise multivariada
Usando o facet_wrap do ggplot, é possível criar
diversos gráficos com poucas linhas. Usaremos sempre as mesmas variáveis
como X (Log do Preço) e Y (Log do Frete) para todos os gráficos, além de
definir a avaliação (máxima ou não) pela cor dos pontos. Cria-se então
um gráfico para cada combinação de estado + atraso (sim/não).
analysis %>%
mutate(customer_state = fct_lump(customer_state, 5)) %>%
ggplot(aes(x = log(price),
y = log(freight_value),
col = high_review)) +
geom_point(alpha = 0.5) +
facet_wrap(customer_state ~ late,
labeller = "label_both") +
ggtitle('Preço vs. Frete vs. Avaliação vs. Estado vs. Atraso') +
theme_light()

Salvando a base de dados para modelagem
Seleção de variáveis
A variável model recebe uma versão modificada da nossa tabela
completa de análise, utilizando apenas as colunas que podem ser
necessárias em uma modelagem futura.
model <- analysis %>%
mutate(delivery_delay = as.numeric(delivery_delay),
log_price = log(price),
freight_prop_fix = (freight_value + 1) / (price + 1), # evitar -Inf
log_freight_prop_fix = log(freight_prop_fix),
product_category_name_cat = fct_lump(product_category_name, 7)
) %>%
replace_na(list(product_photos_qty = 0)) %>%
select(customer_state,
log_price,
log_freight_prop_fix,
payment_type,
product_category_name_cat,
product_photos_qty,
delivery_delay,
late,
high_review_binary)
Checagem final de NAs
Checamos a existência de NAs nessa nova base de dados, agora com
menos colunas. Três colunas possuem NAs, então precisamos resolver isso
antes de pensar em modelagem.
map_df(model, ~sum(is.na(.))) %>%
gather() %>%
arrange(desc(value))
Tratamento dos NAs
Para a coluna de categoria do produto, podemos criar uma categoria
nomeada “NA” ao invés de simplesmente excluir todas as ocorrências. Para
a coluna de avaliação, não há outra saída a não ser excluir. O mesmo se
aplica ao único valor faltante do tipo de pagamento, totalizando 677
linhas a serem removidas. Um número relativamente baixo perante o
total.
model <- model %>%
filter(!is.na(high_review_binary)) %>%
filter(!is.na(payment_type)) %>%
mutate(product_category_name_cat = fct_na_value_to_level(product_category_name_cat))
map_df(model, ~sum(is.na(.))) %>%
gather() %>%
arrange(desc(value))
saveRDS(model, 'data/model.rds')
LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQoqKkltcG9ydGFuZG8gb3MgcGFjb3RlcyBlIGZ1bsOnw7VlcyoqDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpzb3VyY2UoJ21pc2MvcGFja2FnZXMuUicpDQpzb3VyY2UoJ21pc2MvZnVuY3Rpb25zLlInKQ0KYGBgDQoNCiMgKipFREEgZG8gZGF0YXNldCBkYSBPbGlzdCBkZSBlLWNvbW1lcmNlIG5vIEJyYXNpbCoqDQoNCkVzdGUgY29uanVudG8gZGUgZGFkb3Mgw6kgdW1hIHJlcHJlc2VudGHDp8OjbyBkZSBpbmZvcm1hw6fDtWVzIGRldGFsaGFkYXMgc29icmUgMTAwIG1pbCBwZWRpZG9zIGZlaXRvcyBuYSBPbGlzdCBTdG9yZSBkdXJhbnRlIG8gcGVyw61vZG8gZGUgMjAxNiBhIDIwMTgsIGFicmFuZ2VuZG8gZGl2ZXJzYXMgcGxhdGFmb3JtYXMgZGUgdmVuZGEgbm8gQnJhc2lsLiBPcyByZWN1cnNvcyBwcmVzZW50ZXMgbmVzdGUgY29uanVudG8gZGUgZGFkb3Mgbm9zIHBlcm1pdGVtIGV4cGxvcmFyIG9zIHBlZGlkb3Mgc29iIGRpdmVyc2FzIHBlcnNwZWN0aXZhcywgZGVzZGUgbyBzdGF0dXMgZG8gcGVkaWRvLCBwcmXDp28sIHBhZ2FtZW50byBlIGRlc2VtcGVuaG8gZGUgZnJldGUsIGF0w6kgYSBsb2NhbGl6YcOnw6NvIGRvIGNsaWVudGUsIGF0cmlidXRvcyBkb3MgcHJvZHV0b3MgZSwgcG9yIGZpbSwgYXZhbGlhw6fDtWVzIGVzY3JpdGFzIHBlbG9zIGNsaWVudGVzLiDDiSBpbXBvcnRhbnRlIHJlc3NhbHRhciBxdWUgZXNzZXMgZGFkb3MgY29tZXJjaWFpcyBzw6NvIHJlYWlzLCBlbWJvcmEgdGVuaGFtIHNpZG8gYW5vbmltaXphZG9zLg0KDQpFc3NlIGNvbmp1bnRvIGRlIGRhZG9zIGZvaSBnZW50aWxtZW50ZSBmb3JuZWNpZG8gcGVsYSBPbGlzdCwgYSBtYWlvciBsb2phIGRlIGRlcGFydGFtZW50b3MgZW0gbWFya2V0cGxhY2VzIGJyYXNpbGVpcm9zLiBBIE9saXN0IGNvbmVjdGEgcGVxdWVub3MgbmVnw7NjaW9zIGRlIHRvZG8gbyBCcmFzaWwgYSBjYW5haXMgZGUgdmVuZGEgZGUgZm9ybWEgc2ltcGxpZmljYWRhLCBjb20gdW0gw7puaWNvIGNvbnRyYXRvLiBFc3NlcyBjb21lcmNpYW50ZXMgdMOqbSBhIGNhcGFjaWRhZGUgZGUgdmVuZGVyIHNldXMgcHJvZHV0b3MgYXRyYXbDqXMgZGEgT2xpc3QgU3RvcmUgZSBlbnZpw6EtbG9zIGRpcmV0YW1lbnRlIGFvcyBjbGllbnRlcyB1dGlsaXphbmRvIG9zIHBhcmNlaXJvcyBsb2fDrXN0aWNvcyBkYSBPbGlzdC4NCg0KTm8gc2lzdGVtYSBkYSBPbGlzdCBjYWRhIHBlZGlkbyDDqSBkZXNpZ25hZG8gYSB1bSAqKmN1c3RvbWVyaWQqKiDDum5pY28uIElzc28gc2lnbmlmaWNhIHF1ZSBjYWRhIGNvbnN1bWlkb3IgdGVyw6EgKipkaWZlcmVudGVzIGlkcyBwYXJhIGRpZmVyZW50ZXMgcGVkaWRvcyoqLiBPIHByb3DDs3NpdG8gZGUgdGVyIHVtICoqY3VzdG9tZXJ1bmlxdWVfaWQqKiDDum5pY28gcG9yIHBlc3NvYSAoY29tbyBzZSBmb3NzZSB1bSBDUEYpIG5hIGJhc2Ugw6kgcGVybWl0aXIgaWRlbnRpZmljYXIgY29uc3VtaWRvcmVzIHF1ZSBmaXplcmFtIHJlY29tcHJhcyBuYSBsb2phLiBDYXNvIGNvbnRyw6FyaW8sIHZvY8OqIGVuY29udHJhcmlhIHF1ZSBjYWRhIG9yZGVtIHNlbXByZSB0aXZlc3NlIGRpZmVyZW50ZXMgY29uc3VtaWRvcmVzIGFzc29jaWFkb3MuDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpjdXN0b21lcnMgPC0gcmVhZF9jc3YoJ2RhdGEvb2xpc3RfY3VzdG9tZXJzX2RhdGFzZXQuY3N2JykNCm9yZGVycyA8LSByZWFkX2NzdignZGF0YS9vbGlzdF9vcmRlcnNfZGF0YXNldC5jc3YnKQ0KcmV2aWV3cyA8LSByZWFkX2NzdignZGF0YS9vbGlzdF9vcmRlcl9yZXZpZXdzX2RhdGFzZXQuY3N2JykNCnBheW1lbnRzIDwtIHJlYWRfY3N2KCdkYXRhL29saXN0X29yZGVyX3BheW1lbnRzX2RhdGFzZXQuY3N2JykNCnByb2R1Y3RzIDwtIHJlYWRfY3N2KCdkYXRhL29saXN0X3Byb2R1Y3RzX2RhdGFzZXQuY3N2JykNCnNlbGxlcnMgPC0gcmVhZF9jc3YoJ2RhdGEvb2xpc3Rfc2VsbGVyc19kYXRhc2V0LmNzdicpDQpjYXRlZ29yaWVzIDwtIHJlYWRfY3N2KCdkYXRhL3Byb2R1Y3RfY2F0ZWdvcnlfbmFtZV90cmFuc2xhdGlvbi5jc3YnKQ0KZ2VvbG9jYXRpb24gPC0gcmVhZF9jc3YoJ2RhdGEvb2xpc3RfZ2VvbG9jYXRpb25fZGF0YXNldC5jc3YnKQ0Kb3JkZXJfaXRlbXMgPC0gcmVhZF9jc3YoJ2RhdGEvb2xpc3Rfb3JkZXJfaXRlbXNfZGF0YXNldC5jc3YnKQ0KDQojIEp1bnRhbmRvIG9zIGRhZG9zIGVtIHVtYSBiYXNlIMO6bmljYSBjb20gZXhjZcOnw6NvIGRvcyBkYWRvcyBkZSBnZW9sb2NhbGl6YcOnw6NvDQpjb21wbGV0ZV9kYXRhIDwtIGN1c3RvbWVycyAlPiUgDQogIGxlZnRfam9pbihvcmRlcnMpICU+JSANCiAgbGVmdF9qb2luKG9yZGVyX2l0ZW1zKSAlPiUgIyBVbWEgb3JkZW0gcG9kZSB0ZXIgbXVpdG8gaXRlbnMgZSBwb3IgaXNzbyBhIGJhc2UgZXhwYW5kZSBhcXVpDQogIGxlZnRfam9pbihyZXZpZXdzKSAlPiUgDQogIGxlZnRfam9pbihwYXltZW50cykgJT4lIA0KICBsZWZ0X2pvaW4ocHJvZHVjdHMpICU+JQ0KICBsZWZ0X2pvaW4oc2VsbGVycykgJT4lIA0KICBsZWZ0X2pvaW4oY2F0ZWdvcmllcykNCmBgYA0KDQojIyBQcmltZWlyYXMgb2JzZXJ2YcOnw7VlcyBub3MgZGFkb3MNCg0KYGBge3J9DQpza2ltKGNvbXBsZXRlX2RhdGEpDQpgYGANCg0KQSBhbsOhbGlzZSBwb3IgaXRlbSBleHBhbmRlIGEgYmFzZSwgbWFzIGEgbWFpb3JpYSBkb3MgcGVkaWRvcyAoODcuNiUpIGNvbnNpc3RlIGVtIGFwZW5hcyAxIGl0ZW0sIGNvbW8gasOhIGVyYSBkZSBzZSBlc3BlcmFyLiBQYXJhIHNpbXBsaWZpY2FyLCB2YW1vcyBsaW1pdGFyIG5vc3NhIGFuw6FsaXNlIHNvbWVudGUgYSBwZWRpZG9zIGNvbSB1bSBpdGVtLg0KDQpgYGB7cn0NCm9yZGVyX2l0ZW1zICU+JSANCiAgY291bnQob3JkZXJfaXRlbV9pZCkgJT4lICMgTsO6bWVybyBkZSBpdGVucyBkbyBtZXNtbyBwZWRpZG8NCiAgbXV0YXRlKHByb3AgPSByb3VuZChuIC8gc3VtKG4pICogMTAwLCAyKSkNCg0KY29tcGxldGVfZGF0YSA8LSBjb21wbGV0ZV9kYXRhICU+JSANCiAgZmlsdGVyKG9yZGVyX2l0ZW1faWQgPT0gMSkNCmBgYA0KDQojIyBUcmFuc2Zvcm1hbmRvIG9zIGRhZG9zDQoNCk5lc3RhIGV0YXBhLCBhcMOzcyBhIGFuw6FsaXNlIGluaWNpYWwgZGEgZXN0cnV0dXJhIGRvcyBkYWRvcywgY3JpYW1vcyB2YXJpw6F2ZWlzIHF1ZSBzZXLDo28gw7p0ZWlzIG5hIGFuw6FsaXNlIGUgcmVkdXppbW9zIG8gZXNjb3BvIGRhIGFuw6FsaXNlIHNvbWVudGUgcGFyYSBlbnRyZWdhcyBjb25jbHXDrWRhcyAob3JkZXJfc3RhdHVzID09ICdkZWxpdmVyZWQnKS4NCg0KLSAgIFZhcmnDoXZlbCBkZSByZXZpZXcgYWx0bzogIjEiIHNlIHNjb3JlIGRvIHJldmlldyBmb3IgNSwgZSAiMCIgY2FzbyBjb250csOhcmlvLg0KDQotICAgVmFyacOhdmVsIGRlIGRpZmVyZW7Dp2Egbm8gcHJhem8gZGUgZW50cmVnYTogZGlmZXJlbsOnYSBlbnRyZSBvIHByYXpvIGRlIGVudHJlZ2EgZXN0aXB1bGFkbyBwYXJhIG8gY2xpZW50ZSBlIGEgZGF0YSBkZSBlbnRyZWdhIHByb3ByaWFtZW50ZSBkaXRhLg0KDQotICAgVmFyacOhdmVsIGRlIHByb3BvcsOnw6NvIGRvIGZyZXRlOiByZWxhY2lvbmEgdmFsb3JlcyBkZSBGcmV0ZSBlIFByZcOnby4NCg0KLSAgIFZhcmnDoXZlbCBkZSBmcmV0ZSBncmF0dWl0bzogYmluw6FyaWENCg0KYGBge3J9DQphbmFseXNpcyA8LSBjb21wbGV0ZV9kYXRhICU+JSANCiAgbXV0YXRlKGhpZ2hfcmV2aWV3ID0gaWZlbHNlKHJldmlld19zY29yZSA9PSA1LCAnTWF4LiBTY29yZScsICdMZXNzIHRoYW4gNScpLA0KICAgICAgICAgaGlnaF9yZXZpZXdfYmluYXJ5ID0gaWZlbHNlKHJldmlld19zY29yZSA9PSA1LCAxLCAwKSwNCiAgICAgICAgIG9yZGVyX2RlbGl2ZXJlZF9jdXN0b21lcl9kYXRlID0gYXMuRGF0ZShvcmRlcl9kZWxpdmVyZWRfY3VzdG9tZXJfZGF0ZSksDQogICAgICAgICBvcmRlcl9lc3RpbWF0ZWRfZGVsaXZlcnlfZGF0ZSA9IGFzLkRhdGUob3JkZXJfZXN0aW1hdGVkX2RlbGl2ZXJ5X2RhdGUpLA0KICAgICAgICAgZGVsaXZlcnlfZGVsYXkgPSBhcy5udW1lcmljKG9yZGVyX2RlbGl2ZXJlZF9jdXN0b21lcl9kYXRlIC0gb3JkZXJfZXN0aW1hdGVkX2RlbGl2ZXJ5X2RhdGUpLA0KICAgICAgICAgbGF0ZSA9IGlmZWxzZShkZWxpdmVyeV9kZWxheSA+IDAsIDEsIDApLA0KICAgICAgICAgZnJlaWdodF9wcm9wID0gZnJlaWdodF92YWx1ZSAvIHByaWNlLA0KICAgICAgICAgZnJlZV9mcmVpZ2h0ID0gaWZlbHNlKGZyZWlnaHRfdmFsdWUgPT0gMCwgMSwgMCkpICU+JSANCiAgc2VsZWN0KGN1c3RvbWVyX3N0YXRlLCANCiAgICAgICAgIG9yZGVyX3N0YXR1cywgDQogICAgICAgICBwcmljZSwgDQogICAgICAgICBmcmVpZ2h0X3ZhbHVlLCANCiAgICAgICAgIHBheW1lbnRfdHlwZSwgDQogICAgICAgICBwYXltZW50X3ZhbHVlLCANCiAgICAgICAgIHByb2R1Y3RfY2F0ZWdvcnlfbmFtZSwNCiAgICAgICAgIHByb2R1Y3RfcGhvdG9zX3F0eSwNCiAgICAgICAgIHJldmlld19zY29yZSwNCiAgICAgICAgIGhpZ2hfcmV2aWV3LA0KICAgICAgICAgaGlnaF9yZXZpZXdfYmluYXJ5LA0KICAgICAgICAgZGVsaXZlcnlfZGVsYXksDQogICAgICAgICBsYXRlLA0KICAgICAgICAgb3JkZXJfZXN0aW1hdGVkX2RlbGl2ZXJ5X2RhdGUsDQogICAgICAgICBvcmRlcl9kZWxpdmVyZWRfY3VzdG9tZXJfZGF0ZSwNCiAgICAgICAgIHJldmlld19jb21tZW50X3RpdGxlLA0KICAgICAgICAgcmV2aWV3X2NvbW1lbnRfbWVzc2FnZSwNCiAgICAgICAgIGZyZWlnaHRfcHJvcCwNCiAgICAgICAgIGZyZWVfZnJlaWdodCwNCiAgICAgICAgIGN1c3RvbWVyX2NpdHksDQogICAgICAgICBzZWxsZXJfY2l0eSkgJT4lIA0KICBmaWx0ZXIob3JkZXJfc3RhdHVzID09ICdkZWxpdmVyZWQnKQ0KYGBgDQoNClRhbWLDqW0gcHJlY2lzYW1vcyBlbnRlbmRlciBhIGRpc3RyaWJ1acOnw6NvIGRvcyBkYWRvcyBmYWx0YW50ZXMgbm8gZGF0YXNldCwgcG9yIGlzc28gdXNhcmVtb3MgbyBwYWNvdGUgdmlzX2RhdC4gQ29tbyBub3NzYSBiYXNlIGRlIGRhZG9zIMOpIG11aXRvIGdyYW5kZSwgdmFtb3MgcGVnYXIgdW1hIHBhcnRlIG1lbm9yICgzMCUgZG8gdG90YWwpIGUgdGVudGFyIGVudGVuZGVyIHF1YWlzIGNvbHVuYXMgcG9zc3VlbSBtdWl0b3MgZGFkb3MgZmFsdGFuZG8uDQoNCk5hdHVyYWxtZW50ZSwgYXMgY29sdW5hcyBjb20gbWFpb3IgcXVhbnRpZGFkZSBkZSBkYWRvcyBmYWx0YW50ZXMgc8OjbyBhcyBxdWUgcG9zc3VlbSBwcmVlbmNoaW1lbnRvIG9wY2lvbmFsOiB0w610dWxvIGUgbWVuc2FnZW0gZG8gY29tZW50w6FyaW8gZGUgcmV2aXPDo28gZG8gcHJvZHV0by4NCg0KYGBge3J9DQpzZXQuc2VlZCgyMTAxKQ0KdmlzX2RhdChhbmFseXNpcyAlPiUgc2FtcGxlX2ZyYWMoMC4zKSkNCmBgYA0KDQpgYGB7cn0NCm1hcF9kZihhbmFseXNpcywgfnN1bShpcy5uYSguKSkpICU+JSANCiAgZ2F0aGVyKCkgJT4lIA0KICBhcnJhbmdlKGRlc2ModmFsdWUpKQ0KYGBgDQoNCk91dHJvIGZhdG8gcXVlIHByZWNpc2Ftb3MgY29ycmlnaXIgYW50ZXMgZGUgcXVhbHF1ZXIgYW7DoWxpc2Ugw6kgYSBleGlzdMOqbmNpYSBkZSB2YWxvcmVzIG51bG9zIG5hIGNvbHVuYSAibGF0ZSIsIHF1ZSDDqSB1bWEgY29sdW5hIGJpbsOhcmlhIGluZGljYW5kbyBhIHByZXNlbsOnYSBvdSBhdXPDqm5jaWEgZGUgdW0gYXRyYXNvIG5hIGVudHJlZ2EuIElzc28gb2NvcnJlIHBvcnF1ZSBhbGd1bnMgcGVkaWRvcyBuw6NvIHBvc3N1ZW0gZGF0YSBkZSBlbnRyZWdhLiBQb3IgaXNzbyBpcmVtb3MgcmVtb3ZlciBlc3NhcyBvY29ycsOqbmNpYXMuDQoNCmBgYHtyfQ0KYW5hbHlzaXMgPC0gYW5hbHlzaXMgJT4lIA0KICBkcm9wX25hKGxhdGUpDQpgYGANCg0KUG9yIMO6bHRpbW8sIHZhbW9zIGNoZWNhciBhcyBjaWRhZGVzIGNvbSBtYWlzIHBlZGlkb3MgZW50cmVndWVzLg0KDQpgYGB7cn0NCmFuYWx5c2lzICU+JSANCiAgY291bnQoY3VzdG9tZXJfY2l0eSkgJT4lDQogIG11dGF0ZShwcm9wID0gcm91bmQobiAvIHN1bShuKSAqIDEwMCwgMikpICU+JQ0KICBhcnJhbmdlKGRlc2MobikpDQpgYGANCg0KIyMgRXhwbG9yYcOnw6NvIGUgdmlzdWFsaXphw6fDo28gZGUgdmFyacOhdmVpcw0KDQojIyMgQXZhbGlhw6fDo28gZG8gcHJvZHV0byAoMSBhIDUpDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQphbmFseXNpcyAlPiUNCiAgY291bnQocmV2aWV3X3Njb3JlKSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gcmV2aWV3X3Njb3JlLCB5ID0gbikpICsNCiAgZ2VvbV9iYXIoc3RhdCA9J2lkZW50aXR5JykgKw0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gbiksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDMpDQpgYGANCg0KIyMjIE5vdGEgbcOheGltYTogc2ltIG91IG7Do28NCg0KYGBge3J9DQphbmFseXNpcyAlPiUNCiAgY291bnQoaGlnaF9yZXZpZXcpICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSBoaWdoX3JldmlldywgeSA9IG4pKSArDQogIGdlb21fYmFyKHN0YXQgPSdpZGVudGl0eScpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IG4pLCB2anVzdCA9IC0wLjUsIHNpemUgPSAzKQ0KYGBgDQoNCiMjIyBQZWRpZG9zIHBvciBVRg0KDQpgYGB7cn0NCmFuYWx5c2lzICU+JQ0KICBjb3VudChjdXN0b21lcl9zdGF0ZSkgJT4lDQogIGdncGxvdChhZXMoeCA9IGN1c3RvbWVyX3N0YXRlLCB5ID0gbikpICsNCiAgZ2VvbV9iYXIoc3RhdCA9J2lkZW50aXR5JykgKw0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gbiksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDMpDQpgYGANCg0KIyMjIFBlZGlkb3MgcG9yIGZvcm1hIGRlIHBhZ2FtZW50bw0KDQpgYGB7cn0NCmFuYWx5c2lzICU+JQ0KICBjb3VudChwYXltZW50X3R5cGUpICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSBwYXltZW50X3R5cGUsIHkgPSBuKSkgKw0KICBnZW9tX2JhcihzdGF0ID0naWRlbnRpdHknKSArDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSBuKSwgdmp1c3QgPSAtMC41LCBzaXplID0gMykNCmBgYA0KDQojIyMgUGVkaWRvcyBwb3IgY2F0ZWdvcmlhIGRvIHByb2R1dG8NCg0KYGBge3J9DQphbmFseXNpcyAlPiUgDQogIGNvdW50KHByb2R1Y3RfY2F0ZWdvcnlfbmFtZSwgc29ydCA9IFQpICU+JSANCiAgbXV0YXRlKHByb3AgPSByb3VuZChuIC8gc3VtKG4pICogMTAwLCAyKSkNCmBgYA0KDQojIyMgQW7DoWxpc2UgZGUgcHJlw6dvcw0KDQpgYGB7cn0NCnN1bW1hcnkoYW5hbHlzaXMkcHJpY2UpDQpgYGANCg0KYGBge3J9DQpwMSA8LSBhbmFseXNpcyAlPiUgDQogIGdncGxvdChhZXMoeSA9IHByaWNlKSkgKw0KICBnZW9tX2JveHBsb3QoKSArDQogIGdndGl0bGUoJ0JveHBsb3QnKQ0KDQpwMiA8LSBhbmFseXNpcyAlPiUgDQogIGdncGxvdChhZXMoeCA9IHByaWNlKSkgKw0KICBnZW9tX2RlbnNpdHkoKSArDQogIGdndGl0bGUoJ0RlbnNpZGFkZScpDQoNCmdyaWQuYXJyYW5nZShwMSwgcDIsIG5yb3cgPSAxKQ0KYGBgDQoNCkVzc2FzIGFuw6FsaXNlcyBub3MgaW5mb3JtYW0gcXVlIGEgdmFyacOhdmVsIFByZcOnbyDDqSBleHRyZW1hbWVudGUgYXNzaW3DqXRyaWNhLCBpbmNsdXNpdmUgY29tIG91dGxpZXJzIGNvbSBwcmXDp29zIGFjaW1hIGRlIFJcJCA2MDAwLiBPIG1haXMgY29tdW0gYXF1aSwgY2FzbyBzZWphIGRlc2Vqw6F2ZWwgaW5jbHXDrS1sYSBuYSBtb2RlbGFnZW0sIHNlcmlhIHRyYW5zZm9ybWFyIGVzc2EgdmFyacOhdmVsIGUgZXN0YWJpbGl6w6EtbGEuIFBvciBpc3NvLCBhIHBhcnRpciBkZSBhZ29yYSB1c2FyZW1vcyBvIGxvZ2FyaXRtbyBkYSB2YXJpw6F2ZWwgUHJlw6dvLg0KDQpMZW1icmFuZG8gcXVlIG8gQm94cGxvdCBwb2RlIHNlciBwcm9ibGVtw6F0aWNvIHBvciBuw6NvIHJlZmxldGlyIGEgZGlzdHJpYnVpw6fDo28gZG9zIGRhZG9zLiBWZXJlbW9zIGFkaWFudGUgdW0gZXhlbXBsbyBtZWxob3IsIGNvbSBWaW9saW4gUGxvdHMuDQoNCmBgYHtyfQ0KcDFsb2cgPC0gYW5hbHlzaXMgJT4lIA0KICBnZ3Bsb3QoYWVzKHkgPSBsb2cocHJpY2UpKSkgKw0KICBnZW9tX2JveHBsb3QoKSArDQogIGdndGl0bGUoJ0JveHBsb3QnKQ0KDQpwMmxvZyA8LSBhbmFseXNpcyAlPiUgDQogIGdncGxvdChhZXMoeCA9IGxvZyhwcmljZSkpKSArDQogIGdlb21fZGVuc2l0eSgpICsNCiAgZ2d0aXRsZSgnRGVuc2lkYWRlJykNCg0KZ3JpZC5hcnJhbmdlKHAxbG9nLCBwMmxvZywgbnJvdyA9IDEpDQpgYGANCg0KIyMjIEFuw6FsaXNlIGRlIGZyZXRlDQoNCmBgYHtyfQ0Kc3VtbWFyeShhbmFseXNpcyRmcmVpZ2h0X3ZhbHVlKQ0KYGBgDQoNCjc1JSBkb3MgcHJvZHV0b3MgdGl2ZXJhbSB1bSBmcmV0ZSBpbmZlcmlvciBhIDIxIHJlYWlzLCBtYXMgw6kgcG9zc8OtdmVsIHZlciBxdWUgdW0gcGVkaWRvIGVtIGVzcGVjw61maWNvIHRldmUgdW0gZnJldGUgc3VwZXJpb3IgYSA0MDAgcmVhaXMhDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQphbmFseXNpcyAlPiUgDQogIGdncGxvdChhZXMoeCA9IGxvZyhmcmVpZ2h0X3ZhbHVlKSkpICsNCiAgZ2VvbV9kZW5zaXR5KCkgKw0KICBnZ3RpdGxlKCdWYXJpw6F2ZWwgRnJldGUgY29tIFRyYW5zZm9ybWHDp8OjbyBMb2dhcsOtdG1pY2EnKQ0KYGBgDQoNCkEgdmFyacOhdmVsIGRlIEZyZXRlIHBhcmVjZSBzZXIgbWFpcyBwcm9ibGVtw6F0aWNhLCBwb2lzIHRlbSB2YWxvcmVzIDAgZSBhbGd1bnMgc2FsdG9zLiBBIG1lbGhvciBtYW5laXJhIGRlIGlkZW50aWZpY2FyIHJhcGlkYW1lbnRlIG9uZGUgZXN0w6NvIGVzc2VzIHBvbnRvcyBkZSBjb3J0ZSDDqSBhdHJhdsOpcyBkZSBpbnRlcmF0aXZpZGFkZSBncsOhZmljYSENCg0KYGBge3J9DQooYW5hbHlzaXMgJT4lIA0KICBmaWx0ZXIoZnJlaWdodF92YWx1ZSA+IDApICU+JSAjIFRpcmEgb3MgZnJldGVzIGdyYXR1aXRvcw0KICBnZ3Bsb3QoYWVzKHggPSBmcmVpZ2h0X3ZhbHVlKSkgKw0KICBnZW9tX2RlbnNpdHkoKSkgJT4lIGdncGxvdGx5KCkNCmBgYA0KDQpPIHBvbnRvIG5vIGVudG9ybm8gZG8gMTAgcGFyZWNlIHVtIHBvbnRvIGRlIGF0ZW7Dp8OjbyBwZWxhIHN1YmlkYSDDrW5ncmVtZSBhcMOzcyBlbGUuIFRlbW9zIG11aXRvcyB2YWxvcmVzIGRlIGZyZXRlIHJlcGV0aWRvcy4gVGFsdmV6IHNlcmlhIGludGVyZXNzYW50ZSB0cmFuc2Zvcm1hciBlc3NhIHZhcmnDoXZlbCBlbSBjYXRlZ8OzcmljYS4NCg0KQWzDqW0gZGlzc28sIG8gZnJldGUgZGV2ZSB0ZXIgcmVsYcOnw6NvIGNvbSBhIGRpc3TDom5jaWEgZW50cmUgY2xpZW50ZSBlIHZlbmRlZG9yLCBlIHVtYSBhbsOhbGlzZSBmdXR1cmEgcG9kZXJpYSBsZXZhciBpc3NvIGVtIGNvbnNpZGVyYcOnw6NvIGZhemVuZG8gYWxndW0gdGlwbyBkZSBtYW5pcHVsYcOnw6NvIGRlIGxvY2FsaXphw6fDo28sIG1hcyB0YWx2ZXogb3MgZGFkb3MgbsOjbyBwZXJtaXRhbSBmYXplciBlc3NhIGFuw6FsaXNlIGRlIG1hbmVpcmEgZsOhY2lsLg0KDQojIyMgQW7DoWxpc2UgZGUgZnJldGUgc29icmUgcHJlw6dvDQoNCmBgYHtyfQ0Kc3VtbWFyeShhbmFseXNpcyRmcmVpZ2h0X3Byb3ApDQpgYGANCg0KQXF1aSBtYWlzIHVtYSB2ZXogdmVtb3MgdW1hIHByb3BvcsOnw6NvIGFic3VyZGEgZGUgZnJldGUgc29icmUgcHJlw6dvLiBFbSB1bSBwZWRpZG8gZXNwZWPDrWZpY28sIG8gZnJldGUgY29ycmVzcG9uZGV1IGEgMjEuNCUgZG8gdmFsb3IgdG90YWwgZGEgY29tcHJhLg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KYW5hbHlzaXMgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSBsb2coZnJlaWdodF9wcm9wKSkpICsNCiAgZ2VvbV9kZW5zaXR5KCkNCmBgYA0KDQpPIGxvZ2FyaXRtbyBkZXNzYSB2YXJpw6F2ZWwsIHF1YW5kbyBvIGZyZXRlIMOpIHplcm8sIHJlc3VsdGEgZW0gLUluZmluaXRvLiBJc3NvIGNhdXNhcmlhIHByb2JsZW1hcyBuYSBob3JhIGRlIGNyaWFyIG9zIG1vZGVsb3MsIG1hcyBwb2RlbW9zIHJlc29sdmVyIGZhY2lsbWVudGU6IGJhc3RhIGFjcmVzY2VudGFyIHVtIHZhbG9yIGlycmlzw7NyaW8gYW8gZnJldGUsIGRlIG1vZG8gcXVlIGEgZGl2aXPDo28gbsOjbyByZXN1bHRlIGVtIHplcm8uDQoNCiMjIyBBbsOhbGlzZSBkZSBmcmV0ZSB4IHByZcOnbw0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KYW5hbHlzaXMgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSBsb2cocHJpY2UpLA0KICAgICAgICAgICAgIHkgPSBsb2coZnJlaWdodF92YWx1ZSkpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fc21vb3RoKCkNCmBgYA0KDQojIyMgRnJldGVzIGdyYXR1aXRvcw0KDQpgYGB7cn0NCmFuYWx5c2lzICU+JSANCiAgY291bnQoZnJlZV9mcmVpZ2h0LCBzb3J0ID0gVCkgJT4lIA0KICBtdXRhdGUocHJvcCA9IG4gLyBzdW0obikpDQpgYGANCg0KT3MgcGVkaWRvcyBjb20gZnJldGUgZ3JhdHVpdG8gZXF1aXZhbGVtIGEgbWVub3MgZGUgMCw0JSBkbyB0b3RhbC4gw4kgdW1hIHBvcsOnw6NvIHF1YXNlIGlycmVsZXZhbnRlIGRvcyBkYWRvcywgZW50w6NvIHBvZGVtb3MgY29uc2lkZXJhciByZW1vdmVyIGVzc2VzIGRhZG9zIHBhcmEgZmFjaWxpdGFyIGEgbW9kZWxhZ2VtIGZ1dHVyYS4NCg0KIyMjIEFuw6FsaXNlIGRlIGF0cmFzb3MgbmFzIGVudHJlZ2FzDQoNCmBgYHtyfQ0Kc3VtbWFyeShhcy5udW1lcmljKGFuYWx5c2lzJGRlbGl2ZXJ5X2RlbGF5KSkNCmBgYA0KDQpgYGB7cn0NCmFuYWx5c2lzICU+JSANCiAgY291bnQobGF0ZSkgJT4lIA0KICBtdXRhdGUocHJvcCA9IG4gLyBzdW0obikpDQpgYGANCg0KUG9kZW1vcyBwZXJjZWJlciBxdWUgNzUlIGRvcyBwZWRpZG9zIGNoZWdhcmFtIGNvbSBubyBtw61uaW1vIDcgZGlhcyBkZSBhbnRlY2Vkw6puY2lhLCBtYXMgZXhpc3RlbSB0YW1iw6ltIHBlZGlkb3MgY29tIGF0cmFzb3MgYWJzdXJkb3MsIGNoZWdhbmRvIGEgNiBtZXNlcy4gVGFtYsOpbSBkZXNjb2JyaW1vcyBxdWUgbWVub3MgZGUgNyUgZG9zIHBlZGlkb3MgYW5hbGlzYWRvcyBmb3JhbSBlbnRyZWd1ZXMgY29tIGF0cmFzby4NCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmFuYWx5c2lzICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gZGVsaXZlcnlfZGVsYXkpKSArDQogIGdlb21fZGVuc2l0eSgpDQpgYGANCg0KQXF1aSBwb2RlbW9zIHZlciB1bWEgbGV2ZSBpbmNsaW5hw6fDo28gYSBwYXJ0aXIgZG8gMCwgaW5kaWNhbmRvIG9zIHBlZGlkb3MgYXRyYXNhZG9zLiBPIGluY2zDrW5pbyDDqSBiZW0gcGVxdWVubyBlIGrDoSBzYWJlbW9zIHF1ZSBob3V2ZXJhbSByZWxhdGl2YW1lbnRlIHBvdWNvcyBwZWRpZG9zIGF0cmFzYWRvcy4NCg0KIyMjIFF1YW50aWRhZGUgZGUgZm90b3MgZG8gcHJvZHV0bw0KDQpNdWl0b3MgcHJvZHV0b3MgZW0gbWFya2V0cGxhY2VzIGNvc3R1bWFtIGNvbnRhciBjb20gZm90b3MgaWx1c3RyYXRpdmFzIGRvIHByb2R1dG8sIHF1ZSBhanVkYW0gYSBndWlhciBvIGNsaWVudGUgbmEgaG9yYSBkYSBjb21wcmEuDQoNCmBgYHtyfQ0KYW5hbHlzaXMgJT4lIA0KICBjb3VudChwcm9kdWN0X3Bob3Rvc19xdHkpICU+JSANCiAgbXV0YXRlKHByb3AgPSBuIC8gc3VtKG4pKQ0KYGBgDQoNCiMjIE3Dumx0aXBsYXMgZGVuc2lkYWRlcyBQcmXDp28gdnMuIENhdGVnb3JpYQ0KDQpWYW1vcyBhbmFsaXNhciBhIGRlbnNpZGFkZSBkbyBwcmXDp28gcG9yIGNhdGVnb3JpYS4gTm92YW1lbnRlIHVzYW1vcyAqKmZjdF9sdW1wKiogcGFyYSBhZ3J1cGFyIGFzIGNhdGVnb3JpYXMgZm9yYSBkbyB0b3AgNyBlbSBmcmVxdcOqbmNpYSBlIGNyaWFtb3MgdW0gZ3LDoWZpY28gY29tIHVtYSBjdXJ2YSBkZSBkZW5zaWRhZGUgcGFyYSBvIHByZcOnbyB2cy4gY2FkYSBjYXRlZ29yaWEuIENvbW8gYXMgY3VydmFzIHNlIHNvYnJlcMO1ZW0sIGEgdmlzdWFsaXphw6fDo28gw6kgZGlmaWN1bHRhZGEuDQoNCmBgYHtyfQ0KYW5hbHlzaXMgJT4lIA0KICBtdXRhdGUoY2F0X3Byb2R1Y3RfY2F0ZWdvcnlfbmFtZSA9IGZjdF9sdW1wKHByb2R1Y3RfY2F0ZWdvcnlfbmFtZSwgbiA9IDcpKSAlPiUgDQogIGdncGxvdChhZXMoeCA9IGxvZyhwcmljZSksIGZpbGwgPSBjYXRfcHJvZHVjdF9jYXRlZ29yeV9uYW1lKSkgKw0KICBnZW9tX2RlbnNpdHkoYWxwaGEgPSAwLjM1KQ0KYGBgDQoNClVtIG1vZG8gbWVsaG9yIGRlIHZpc3VhbGl6YXIgc2VyaWEgY29sb2NhciBhIGJhc2UgZGUgY2FkYSBjdXJ2YSBlbSB1bSBuw612ZWwgZGlmZXJlbnRlLiBPIGdyw6FmaWNvIGFjaW1hIG5vcyBtb3N0cmF2YSBhIGV4aXN0w6puY2lhIGRlIE5BcyBuYSBjb2x1bmEsIGVudMOjbyB2YW1vcyB0YW1iw6ltIHJlbW92ZXIuDQoNCmBgYHtyfQ0KYW5hbHlzaXMgJT4lDQogIGZpbHRlcighaXMubmEocHJvZHVjdF9jYXRlZ29yeV9uYW1lKSkgJT4lIA0KICBtdXRhdGUoY2F0X3Byb2R1Y3RfY2F0ZWdvcnlfbmFtZSA9IGZjdF9sdW1wKHByb2R1Y3RfY2F0ZWdvcnlfbmFtZSwgbiA9IDcpLA0KICAgICAgICAgbG9nX3ByaWNlID0gbG9nKHByaWNlKSkgJT4lDQogIGdncGxvdChhZXMoeCA9IGxvZ19wcmljZSwgDQogICAgICAgICAgICAgeSA9IGNhdF9wcm9kdWN0X2NhdGVnb3J5X25hbWUsDQogICAgICAgICAgICAgZmlsbCA9IHN0YXQoeCkpKSArIA0KICBnZW9tX2RlbnNpdHlfcmlkZ2VzX2dyYWRpZW50KHNjYWxlID0gMywgcmVsX21pbl9oZWlnaHQgPSAwLjAwMSkgKw0KICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhuYW1lID0gIkxvZyBkbyBQcmXDp28iKQ0KYGBgDQoNCk91dHJvIG1vZG8gZGUgYW5hbGlzYXIgb3MgdmFsb3JlcyBwb3IgY2F0ZWdvcmlhIHNlcmlhIGNyaWFuZG8gdW0gYm94cGxvdCBwYXJhIGNhZGEsIHNhYmVuZG8gZGEgbGltaXRhw6fDo28gbmF0dXJhbCBkbyBib3hwbG90IGVtIG7Do28gcmVmbGV0aXIgYmVtIGEgZGlzdHJpYnVpw6fDo28vZnJlcXXDqm5jaWEgZG9zIGRhZG9zLg0KDQpgYGB7cn0NCmFuYWx5c2lzICU+JSANCiAgZmlsdGVyKCFpcy5uYShwcm9kdWN0X2NhdGVnb3J5X25hbWUpKSAlPiUNCiAgbXV0YXRlKGNhdF9wcm9kdWN0X2NhdGVnb3J5X25hbWUgPSBmY3RfbHVtcChwcm9kdWN0X2NhdGVnb3J5X25hbWUsIG4gPSA3KSwNCiAgICAgICAgIGxvZ19wcmljZSA9IGxvZyhwcmljZSkpICU+JSANCiAgZ2dwbG90KGFlcyhmaWxsID0gY2F0X3Byb2R1Y3RfY2F0ZWdvcnlfbmFtZSwNCiAgICAgICAgICAgICB5ID0gbG9nX3ByaWNlLA0KICAgICAgICAgICAgIHggPSBjYXRfcHJvZHVjdF9jYXRlZ29yeV9uYW1lKSkgKw0KICBnZW9tX2JveHBsb3QoKSArDQogIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSkpDQpgYGANCg0KQ29tIGFsZ3VtYXMgbXVkYW7Dp2FzIG5vIGPDs2RpZ28gYW50ZXJpb3IsIHBvZGVtb3MgdHJhbnNmb3JtYXIgb3MgYm94IHBsb3RzIGVtIHZpb2xpbiBwbG90cywgcXVlIG1vc3RyYW0gY29tIGNsYXJlemEgYSBkaXN0cmlidWnDp8OjbyBkb3MgZGFkb3MuIEEgbGltaXRhw6fDo28gbmF0dXJhbCBkbyB2aW9saW4gcGxvdCBubyBnZ3Bsb3QgZW0gUiDDqSBhIGF1c8OqbmNpYSBkYXMgbGluaGFzIHBlcmNlbnRpcywgZW50w6NvIHBvZGVtb3MgYWRpY2lvbmFyIHVtIGJveCBwbG90IGRlbnRybyBkZSBjYWRhIHZpb2xpbiBwbG90IGUgb2J0ZXIgbyAibWVsaG9yIGRvcyBkb2lzIG11bmRvcyINCg0KYGBge3J9DQphbmFseXNpcyAlPiUNCiAgZmlsdGVyKCFpcy5uYShwcm9kdWN0X2NhdGVnb3J5X25hbWUpKSAlPiUNCiAgbXV0YXRlKGNhdF9wcm9kdWN0X2NhdGVnb3J5X25hbWUgPSBmY3RfbHVtcChwcm9kdWN0X2NhdGVnb3J5X25hbWUsIG4gPSA3KSwNCiAgICAgICAgIGxvZ19wcmljZSA9IGxvZyhwcmljZSkpICU+JSANCiAgZ2dwbG90KGFlcyhmaWxsID0gY2F0X3Byb2R1Y3RfY2F0ZWdvcnlfbmFtZSwNCiAgICAgICAgICAgICB5ID0gbG9nX3ByaWNlLA0KICAgICAgICAgICAgIHggPSBjYXRfcHJvZHVjdF9jYXRlZ29yeV9uYW1lKSkgKw0KICBnZW9tX3Zpb2xpbih0cmltID0gRkFMU0UpICsgICMgdHJpbSA9IEZBTFNFIHNob3dzIHRoZSBmdWxsIGRpc3RyaWJ1dGlvbg0KICBnZW9tX2JveHBsb3Qod2lkdGggPSAwLjIsIG91dGxpZXIuc2hhcGUgPSBOQSwgcG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSh3aWR0aCA9IDAuNzUpKSArDQogIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSkpDQpgYGANCg0KIyMgQW7DoWxpc2VzIGJpdmFyaWFkYXMNCg0KIyMjIEVzdGFkbyB2cy4gQXZhbGlhw6fDo28gZG8gY2xpZW50ZQ0KDQpgYGB7cn0NCmFuYWx5c2lzICU+JQ0KICBmaWx0ZXIoIWlzLm5hKGhpZ2hfcmV2aWV3X2JpbmFyeSkpICU+JQ0KICBncm91cF9ieShjdXN0b21lcl9zdGF0ZSkgJT4lDQogIHN1bW1hcml6ZSgNCiAgICBuID0gbigpLA0KICAgIG1heF9yZXZpZXdzID0gc3VtKGhpZ2hfcmV2aWV3X2JpbmFyeSksDQogICAgbWF4X3Jldmlld3NfcHJvcCA9IG1lYW4oaGlnaF9yZXZpZXdfYmluYXJ5KSkgJT4lDQogIGFycmFuZ2UoZGVzYyhtYXhfcmV2aWV3c19wcm9wKSkNCmBgYA0KDQpNZXNtbyBTw6NvIFBhdWxvIHNlbmRvIGRlIG1hbmVpcmEgZGlzcGFyYWRhIG8gZXN0YWRvIGNvbSBtYWlzIHBlZGlkb3MsIHRhbWLDqW0gw6kgbyBlc3RhZG8gbWFpcyBzYXRpc2ZlaXRvOiA2MS43JSBkb3MgcGVkaWRvcyBmb3JhbSBhdmFsaWFkb3MgY29tIG5vdGEgbcOheGltYS4gVmFtb3MgYWdvcmEgdGVzdGFyIHVtYSB2aXN1YWxpemHDp8OjbyBpbnRlcmF0aXZhIGNvbSBQbG90bHkgcXVlIG5vcyBwZXJtaXRpcsOhIHZlciBleGF0YW1lbnRlIG8gbsO6bWVybyBkZSBwZWRpZG9zIHBvciBlc3RhZG8gZSBhICJ0YXhhIGRlIHNhdGlzZmHDp8OjbyIgZGUgY2FkYS4NCg0KQXF1aSB2ZW1vcyBxdWUgbyBlc3RhZG8gbWFpcyBpbnNhdGlzZmVpdG8gc2VyaWEgbyBNYXJhbmjDo28sIG9uZGUgYXBlbmFzIDQ4LjMlIGRvcyA3MzkgcGVkaWRvcyBmb3JhbSBhdmFsaWFkb3MgY29tIGEgbm90YSBtw6F4aW1hLiBEZSBxdWFscXVlciBtYW5laXJhLCBuw6NvIMOpIHBvc3PDrXZlbCB0cmHDp2FyIHVtIHBhcmFsZWxvIGVudHJlIGVzdGFkbyBlIHByb2JhYmlsaWRhZGUgZGUgYXZhbGlhw6fDo28gbcOheGltYS4NCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmFuYWx5c2lzICU+JSANCiAgcGxvdGFfdHhfaW50ZXJlc3NlKHZhcl94ID0gJ2N1c3RvbWVyX3N0YXRlJywNCiAgICAgICAgICAgICAgICAgICAgIHZhcl95ID0gJ2hpZ2hfcmV2aWV3JywNCiAgICAgICAgICAgICAgICAgICAgIGZsYWdfaW50ZXJlc3NlID0gJ01heC4gU2NvcmUnKQ0KYGBgDQoNClZhbW9zIHRlbnRhciBtdWRhciBhIGVzY2FsYSBkYSB2aXN1YWxpemHDp8OjbyBwYXJhIG9idGVyIG1haW9yIGNsYXJlemEuDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQphbmFseXNpcyAlPiUgDQogIHBsb3RhX3R4X2ludGVyZXNzZSh2YXJfeCA9ICdjdXN0b21lcl9zdGF0ZScsDQogICAgICAgICAgICAgICAgICAgICB2YXJfeSA9ICdoaWdoX3JldmlldycsDQogICAgICAgICAgICAgICAgICAgICBmbGFnX2ludGVyZXNzZSA9ICdNYXguIFNjb3JlJywNCiAgICAgICAgICAgICAgICAgICAgIHlsaW0gPSBOQSkNCmBgYA0KDQojIyMgRm9ybWEgZGUgcGFnYW1lbnRvIHZzLiBBdmFsaWHDp8OjbyBkbyBjbGllbnRlDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQphbmFseXNpcyAlPiUgDQogIHBsb3RhX3R4X2ludGVyZXNzZSh2YXJfeCA9ICdwYXltZW50X3R5cGUnLA0KICAgICAgICAgICAgICAgICAgICAgdmFyX3kgPSAnaGlnaF9yZXZpZXcnLA0KICAgICAgICAgICAgICAgICAgICAgZmxhZ19pbnRlcmVzc2UgPSAnTWF4LiBTY29yZScsDQogICAgICAgICAgICAgICAgICAgICB5bGltID0gTkEpDQpgYGANCg0KIyMjIENhdGVnb3JpYSBkbyBwcm9kdXRvIHZzLiBBdmFsaWHDp8OjbyBkbyBjbGllbnRlDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQphbmFseXNpcyAlPiUgDQogIHBsb3RhX3R4X2ludGVyZXNzZSh2YXJfeCA9ICdwcm9kdWN0X2NhdGVnb3J5X25hbWUnLA0KICAgICAgICAgICAgICAgICAgICAgdmFyX3kgPSAnaGlnaF9yZXZpZXcnLA0KICAgICAgICAgICAgICAgICAgICAgZmxhZ19pbnRlcmVzc2UgPSAnTWF4LiBTY29yZScsDQogICAgICAgICAgICAgICAgICAgICB5bGltID0gTkEpDQpgYGANCg0KVGVtb3MgbXVpdGFzIGNsYXNzZXMgZGlmZXJlbnRlcyBlIGlzc28gYWNhYmEgY29tcGxpY2FuZG8gbXVpdG8gYSB2aXN1YWxpemHDp8OjbyBkb3MgZGFkb3MuIFBvciBpc3NvIHZhbW9zIHBlZ2FyIGFzIDEwIG1haXMgcHJlc2VudGVzIGUgYWdydXBhciBvIHJlc3RvIGVtIHVtYSAxMcKqIHZhcmnDoXZlbCwgY2hhbWFkYSBkZSAiT3RoZXIiLiBJc3NvIMOpIHBvc3PDrXZlbCBhdHJhdsOpcyBkYSBmdW7Dp8OjbyAqKmZjdF9sdW1wKiogYSBzZWd1aXINCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmFuYWx5c2lzICU+JSANCiAgbXV0YXRlKHByb2R1Y3RfY2F0ZWdvcnlfbmFtZV9jYXQgPSBmY3RfbHVtcChwcm9kdWN0X2NhdGVnb3J5X25hbWUsIDEwKSkgJT4lIA0KICBwbG90YV90eF9pbnRlcmVzc2UodmFyX3ggPSAncHJvZHVjdF9jYXRlZ29yeV9uYW1lX2NhdCcsDQogICAgICAgICAgICAgICAgICAgICB2YXJfeSA9ICdoaWdoX3JldmlldycsDQogICAgICAgICAgICAgICAgICAgICBmbGFnX2ludGVyZXNzZSA9ICdNYXguIFNjb3JlJywNCiAgICAgICAgICAgICAgICAgICAgIHlsaW0gPSBOQSkNCmBgYA0KDQpBZ29yYSBjb20gbyBncsOhZmljbyBtdWl0byBtYWlzIGxlZ8OtdmVsLCBwb2RlbW9zIHBlcmNlYmVyIHJhcGlkYW1lbnRlIHF1ZSBhcyAzIGNhdGVnb3JpYXMgY29tIG1haW9yIHRheGEgZGUgc2F0aXNmYcOnw6NvIGRvIGNsaWVudGUgKGF2YWxpYcOnw6NvIDUgZXN0cmVsYXMpIHPDo286DQoNCi0gICBCcmlucXVlZG9zICg2Mi44JSkNCg0KLSAgIEJlbGV6YSBlIHNhw7pkZSAoNjIuMSUpDQoNCi0gICBFc3BvcnRlIGUgbGF6ZXIgKDYxLjQlKQ0KDQojIyMgUHJlw6dvIHZzLiBBdmFsaWHDp8OjbyBkbyBjbGllbnRlDQoNCmBgYHtyfQ0KYW5hbHlzaXMgJT4lIA0KICBncm91cF9ieShoaWdoX3JldmlldykgJT4lIA0KICBzdW1tYXJpemUobiA9IG4oKSwNCiAgICAgICAgICAgIG1lYW4gPSBtZWFuKHByaWNlLCBuYS5ybSA9IFQpLA0KICAgICAgICAgICAgc2QgPSBzZChwcmljZSwgbmEucm0gPSBUKSwNCiAgICAgICAgICAgIG1pbiA9IG1pbihwcmljZSwgbmEucm0gPSBUKSwNCiAgICAgICAgICAgIG1heCA9IG1heChwcmljZSwgbmEucm0gPSBUKSkNCmBgYA0KDQojIyMgRnJldGUgdnMuIEF2YWxpYcOnw6NvIGRvIGNsaWVudGUNCg0KYGBge3J9DQphbmFseXNpcyAlPiUgDQogIGdyb3VwX2J5KGhpZ2hfcmV2aWV3KSAlPiUgDQogIHN1bW1hcml6ZShuID0gbigpLA0KICAgICAgICAgICAgbWVhbiA9IG1lYW4oZnJlaWdodF92YWx1ZSwgbmEucm0gPSBUKSwNCiAgICAgICAgICAgIHNkID0gc2QoZnJlaWdodF92YWx1ZSwgbmEucm0gPSBUKSwNCiAgICAgICAgICAgIG1pbiA9IG1pbihmcmVpZ2h0X3ZhbHVlLCBuYS5ybSA9IFQpLA0KICAgICAgICAgICAgbWF4ID0gbWF4KGZyZWlnaHRfdmFsdWUsIG5hLnJtID0gVCkpDQpgYGANCg0KIyMjIEZyZXRlIHNvYnJlIHByZcOnbyB2cy4gQXZhbGlhw6fDo28gZG8gY2xpZW50ZQ0KDQpgYGB7cn0NCmFuYWx5c2lzICU+JSANCiAgZ3JvdXBfYnkoaGlnaF9yZXZpZXcpICU+JSANCiAgc3VtbWFyaXplKG4gPSBuKCksDQogICAgICAgICAgICBtZWFuID0gbWVhbihmcmVpZ2h0X3Byb3AsIG5hLnJtID0gVCksDQogICAgICAgICAgICBzZCA9IHNkKGZyZWlnaHRfcHJvcCwgbmEucm0gPSBUKSwNCiAgICAgICAgICAgIG1pbiA9IG1pbihmcmVpZ2h0X3Byb3AsIG5hLnJtID0gVCksDQogICAgICAgICAgICBtYXggPSBtYXgoZnJlaWdodF9wcm9wLCBuYS5ybSA9IFQpKQ0KYGBgDQoNCiMjIyBQcmXDp28gdnMuIEF2YWxpYcOnw6NvIGRvIGNsaWVudGUNCg0KYGBge3J9DQogYW5hbHlzaXMgJT4lDQogICBtdXRhdGUobG9nX3ByaWNlID0gbG9nKHByaWNlKSkgJT4lIA0KICAgZ2dzdGF0c3Bsb3Q6OmdnYmV0d2VlbnN0YXRzKA0KICAgeCA9IGhpZ2hfcmV2aWV3LA0KICAgeSA9IGxvZ19wcmljZSwNCiAgIHRpdGxlID0gIkxvZyBkbyBQcmXDp28gdnMuIEF2YWxpYcOnw6NvIGRvIENsaWVudGUiKQ0KYGBgDQoNCiMjIyBBdHJhc28gbmEgZW50cmVnYSB2cy4gQXZhbGlhw6fDo28gZG8gY2xpZW50ZQ0KDQpBcXVpIHBvZGVtb3MgcGVyY2ViZXIgdW1hIGNsYXJhIGRpZmVyZW7Dp2EsIHNlbmRvIGVzc2EgdW1hIHZhcmnDoXZlbCBxdWUgY2xhcmFtZW50ZSBpbXBhY3RhIG5hIEF2YWxpYcOnw6NvIE3DoXhpbWEgZG8gY2xpZW50ZS4gQSBjdXJ2YSB2ZXJtZWxoYSBhcMOzcyBvIDAgaW5kaWNhIHF1ZSB1bWEgZ3JhbmRlIHBhcnRlIGRvcyBwZWRpZG9zIGF0cmFzYWRvcyBuw6NvIHJlY2ViZSBub3RhIG3DoXhpbWEuDQoNCmBgYHtyfQ0KYW5hbHlzaXMgJT4lDQogIGZpbHRlcighaXMubmEoaGlnaF9yZXZpZXcpKSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gZGVsaXZlcnlfZGVsYXksIGZpbGwgPSBhcy5mYWN0b3IoaGlnaF9yZXZpZXcpKSkgKw0KICBnZW9tX2RlbnNpdHkoYWxwaGEgPSAwLjUpDQpgYGANCg0KQ29tIHBvdWNhcyBsaW5oYXMgZGUgY8OzZGlnbywgcG9kZW1vcyBwZXJjZWJlciBxdWUgKio4Myw2JSoqIGRvcyBwZWRpZG9zIGF0cmFzYWRvcyBuw6NvIGZvcmFtIGF2YWxpYWRvcyBjb20gbm90YSBtw6F4aW1hDQoNCmBgYHtyfQ0KYW5hbHlzaXMgJT4lDQogIGZpbHRlcighaXMubmEoaGlnaF9yZXZpZXcpKSAlPiUNCiAgZmlsdGVyKGRlbGl2ZXJ5X2RlbGF5ID4gMCkgJT4lDQogIGNvdW50KGhpZ2hfcmV2aWV3KSAlPiUNCiAgbXV0YXRlKHByb2IgPSBuIC8gc3VtKG4pICogMTAwKQ0KYGBgDQoNCiMjIyBWYXJpw6F2ZWwgYmluw6FyaWEgZGUgYXRyYXNvIHZzLiBBdmFsaWHDp8OjbyBkbyBjbGllbnRlDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQphbmFseXNpcyAlPiUgDQogIG11dGF0ZShsYXRlID0gaWZlbHNlKGxhdGUgPT0gMSwgJ1llcycsICdObycpKSAlPiUgDQogIHBsb3RhX3R4X2ludGVyZXNzZSh2YXJfeCA9ICdsYXRlJywNCiAgICAgICAgICAgICAgICAgICAgIHZhcl95ID0gJ2hpZ2hfcmV2aWV3JywNCiAgICAgICAgICAgICAgICAgICAgIGZsYWdfaW50ZXJlc3NlID0gJ01heC4gU2NvcmUnLA0KICAgICAgICAgICAgICAgICAgICAgeWxpbSA9IE5BKQ0KYGBgDQoNCiMjIyBRdWFudGlkYWRlIGRlIGZvdG9zIHZzLiBBdmFsaWHDp8OjbyBkbyBjbGllbnRlDQoNCkFudGVzIGRlIGZhemVyIGVzc2EgYW7DoWxpc2UsIHN1YnN0aXR1w61tb3MgTkEgcG9yIDAgZm90b3MuIERlIHF1YWxxdWVyIG1hbmVpcmEsIG7Do28gw6kgcG9zc8OtdmVsIG5vdGFyIHF1YWxxdWVyIHJlbGHDp8Ojby4NCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmFuYWx5c2lzICU+JSANCiAgcmVwbGFjZV9uYShsaXN0KHByb2R1Y3RfcGhvdG9zX3F0eSA9IDApKSAlPiUgDQogIHBsb3RhX3R4X2ludGVyZXNzZSh2YXJfeCA9ICdwcm9kdWN0X3Bob3Rvc19xdHknLA0KICAgICAgICAgICAgICAgICAgICAgdmFyX3kgPSAnaGlnaF9yZXZpZXcnLA0KICAgICAgICAgICAgICAgICAgICAgZmxhZ19pbnRlcmVzc2UgPSAnTWF4LiBTY29yZScpDQpgYGANCg0KIyMgQW7DoWxpc2UgbXVsdGl2YXJpYWRhDQoNClVzYW5kbyBvICoqZmFjZXRfd3JhcCoqIGRvIGdncGxvdCwgw6kgcG9zc8OtdmVsIGNyaWFyIGRpdmVyc29zIGdyw6FmaWNvcyBjb20gcG91Y2FzIGxpbmhhcy4gVXNhcmVtb3Mgc2VtcHJlIGFzIG1lc21hcyB2YXJpw6F2ZWlzIGNvbW8gWCAoTG9nIGRvIFByZcOnbykgZSBZIChMb2cgZG8gRnJldGUpIHBhcmEgdG9kb3Mgb3MgZ3LDoWZpY29zLCBhbMOpbSBkZSBkZWZpbmlyIGEgYXZhbGlhw6fDo28gKG3DoXhpbWEgb3UgbsOjbykgcGVsYSBjb3IgZG9zIHBvbnRvcy4gQ3JpYS1zZSBlbnTDo28gdW0gZ3LDoWZpY28gcGFyYSBjYWRhIGNvbWJpbmHDp8OjbyBkZSBlc3RhZG8gKyBhdHJhc28gKHNpbS9uw6NvKS4NCg0KYGBge3J9DQphbmFseXNpcyAlPiUNCiAgbXV0YXRlKGN1c3RvbWVyX3N0YXRlID0gZmN0X2x1bXAoY3VzdG9tZXJfc3RhdGUsIDUpKSAlPiUgDQogIGdncGxvdChhZXMoeCA9IGxvZyhwcmljZSksDQogICAgICAgICAgICAgeSA9IGxvZyhmcmVpZ2h0X3ZhbHVlKSwNCiAgICAgICAgICAgICBjb2wgPSBoaWdoX3JldmlldykpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKw0KICBmYWNldF93cmFwKGN1c3RvbWVyX3N0YXRlIH4gbGF0ZSwgDQogICAgICAgICAgICAgbGFiZWxsZXIgPSAibGFiZWxfYm90aCIpICsNCiAgZ2d0aXRsZSgnUHJlw6dvIHZzLiBGcmV0ZSB2cy4gQXZhbGlhw6fDo28gdnMuIEVzdGFkbyB2cy4gQXRyYXNvJykgKw0KICB0aGVtZV9saWdodCgpDQpgYGANCg0KIyMgU2FsdmFuZG8gYSBiYXNlIGRlIGRhZG9zIHBhcmEgbW9kZWxhZ2VtDQoNCiMjIyBTZWxlw6fDo28gZGUgdmFyacOhdmVpcw0KDQpBIHZhcmnDoXZlbCBtb2RlbCByZWNlYmUgdW1hIHZlcnPDo28gbW9kaWZpY2FkYSBkYSBub3NzYSB0YWJlbGEgY29tcGxldGEgZGUgYW7DoWxpc2UsIHV0aWxpemFuZG8gYXBlbmFzIGFzIGNvbHVuYXMgcXVlIHBvZGVtIHNlciBuZWNlc3PDoXJpYXMgZW0gdW1hIG1vZGVsYWdlbSBmdXR1cmEuDQoNCmBgYHtyfQ0KbW9kZWwgPC0gYW5hbHlzaXMgJT4lIA0KICBtdXRhdGUoZGVsaXZlcnlfZGVsYXkgPSBhcy5udW1lcmljKGRlbGl2ZXJ5X2RlbGF5KSwNCiAgICAgICAgIGxvZ19wcmljZSA9IGxvZyhwcmljZSksDQogICAgICAgICBmcmVpZ2h0X3Byb3BfZml4ID0gKGZyZWlnaHRfdmFsdWUgKyAxKSAvIChwcmljZSArIDEpLCAjIGV2aXRhciAtSW5mDQogICAgICAgICBsb2dfZnJlaWdodF9wcm9wX2ZpeCA9IGxvZyhmcmVpZ2h0X3Byb3BfZml4KSwNCiAgICAgICAgIHByb2R1Y3RfY2F0ZWdvcnlfbmFtZV9jYXQgPSBmY3RfbHVtcChwcm9kdWN0X2NhdGVnb3J5X25hbWUsIDcpDQogICAgICAgICApICU+JSANCiAgcmVwbGFjZV9uYShsaXN0KHByb2R1Y3RfcGhvdG9zX3F0eSA9IDApKSAlPiUgDQogIHNlbGVjdChjdXN0b21lcl9zdGF0ZSwNCiAgICAgICAgIGxvZ19wcmljZSwNCiAgICAgICAgIGxvZ19mcmVpZ2h0X3Byb3BfZml4LA0KICAgICAgICAgcGF5bWVudF90eXBlLA0KICAgICAgICAgcHJvZHVjdF9jYXRlZ29yeV9uYW1lX2NhdCwNCiAgICAgICAgIHByb2R1Y3RfcGhvdG9zX3F0eSwNCiAgICAgICAgIGRlbGl2ZXJ5X2RlbGF5LA0KICAgICAgICAgbGF0ZSwNCiAgICAgICAgIGhpZ2hfcmV2aWV3X2JpbmFyeSkNCmBgYA0KDQojIyMgQ2hlY2FnZW0gZmluYWwgZGUgTkFzDQoNCkNoZWNhbW9zIGEgZXhpc3TDqm5jaWEgZGUgTkFzIG5lc3NhIG5vdmEgYmFzZSBkZSBkYWRvcywgYWdvcmEgY29tIG1lbm9zIGNvbHVuYXMuIFRyw6pzIGNvbHVuYXMgcG9zc3VlbSBOQXMsIGVudMOjbyBwcmVjaXNhbW9zIHJlc29sdmVyIGlzc28gYW50ZXMgZGUgcGVuc2FyIGVtIG1vZGVsYWdlbS4NCg0KYGBge3J9DQptYXBfZGYobW9kZWwsIH5zdW0oaXMubmEoLikpKSAlPiUgDQogIGdhdGhlcigpICU+JSANCiAgYXJyYW5nZShkZXNjKHZhbHVlKSkNCmBgYA0KDQojIyMgVHJhdGFtZW50byBkb3MgTkFzDQoNClBhcmEgYSBjb2x1bmEgZGUgY2F0ZWdvcmlhIGRvIHByb2R1dG8sIHBvZGVtb3MgY3JpYXIgdW1hIGNhdGVnb3JpYSBub21lYWRhICJOQSIgYW8gaW52w6lzIGRlIHNpbXBsZXNtZW50ZSBleGNsdWlyIHRvZGFzIGFzIG9jb3Jyw6puY2lhcy4gUGFyYSBhIGNvbHVuYSBkZSBhdmFsaWHDp8OjbywgbsOjbyBow6Egb3V0cmEgc2HDrWRhIGEgbsOjbyBzZXIgZXhjbHVpci4gTyBtZXNtbyBzZSBhcGxpY2EgYW8gw7puaWNvIHZhbG9yIGZhbHRhbnRlIGRvIHRpcG8gZGUgcGFnYW1lbnRvLCB0b3RhbGl6YW5kbyA2NzcgbGluaGFzIGEgc2VyZW0gcmVtb3ZpZGFzLiBVbSBuw7ptZXJvIHJlbGF0aXZhbWVudGUgYmFpeG8gcGVyYW50ZSBvIHRvdGFsLg0KDQpgYGB7cn0NCm1vZGVsIDwtIG1vZGVsICU+JQ0KICBmaWx0ZXIoIWlzLm5hKGhpZ2hfcmV2aWV3X2JpbmFyeSkpICU+JQ0KICBmaWx0ZXIoIWlzLm5hKHBheW1lbnRfdHlwZSkpICU+JQ0KICBtdXRhdGUocHJvZHVjdF9jYXRlZ29yeV9uYW1lX2NhdCA9IGZjdF9uYV92YWx1ZV90b19sZXZlbChwcm9kdWN0X2NhdGVnb3J5X25hbWVfY2F0KSkNCmBgYA0KDQpgYGB7cn0NCm1hcF9kZihtb2RlbCwgfnN1bShpcy5uYSguKSkpICU+JSANCiAgZ2F0aGVyKCkgJT4lIA0KICBhcnJhbmdlKGRlc2ModmFsdWUpKQ0KYGBgDQoNCmBgYHtyfQ0Kc2F2ZVJEUyhtb2RlbCwgJ2RhdGEvbW9kZWwucmRzJykNCmBgYA0K